﻿/* Pierwszy program Direct X w C# */


using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
/* W tym miejscu dołączamy biblioteki */
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;



/* Było narysowane, ale praktycznie w 2D. No to spróbujmy w 3D coś więcej */

namespace DirectX_Sample
{
    public class MyObject
    {
        private Mesh m;                 //Mesh - klasa siatki obiektu
        private Device dev;             
        private ExtendedMaterial[] ExtendedMaterials;   // Tablica ExtendedMateriałów obiektu, z której wydobędziemy
        private Texture[] Textures;                     // tekstury
        private Material[] Materials;                   // i materiały
        public Vector3 center, BoxMin, BoxMax;          // center, radius - sfera otaczająca
        public float radius;                            // BoxMin,BoxMax - box otaczający

        public MyObject(ref Device d)             // prosty konstruktor
        {
            m=null;
            dev = d;
        }

        public void Render()                // Rendering obiektu na device
        {
            dev.VertexFormat = m.VertexFormat;   //Ustalamy Format Vertexów
            for (int i = 0; i < ExtendedMaterials.Length; i++)      //EM.Length zawiera liczbę submeshów danego mesha
            {
                dev.Material = Materials[i];            //każdy submesh ma inny materiał
                dev.SetTexture(0, Textures[i]);         //i inną teksturę
                m.DrawSubset(i);                        //rendering i-tego submesha
            }
        }

        public void LoadFromFile(string FileName)
        {
            m = Mesh.FromFile(FileName, MeshFlags.Managed, dev, out ExtendedMaterials); //Tu wczytujemy plik .x
            
            VertexBuffer vertices = m.VertexBuffer;  // za pomocą klasy Geometry liczymy Bounding Box i Bounding Sphere
            GraphicsStream stream = vertices.Lock(0, 0, LockFlags.None);
            radius = Geometry.ComputeBoundingSphere(stream, m.NumberVertices, m.VertexFormat, out center);
            Geometry.ComputeBoundingBox(stream, m.NumberVertices, m.VertexFormat, out BoxMin, out BoxMax);
            vertices.Unlock();
            
            if ((ExtendedMaterials != null) && (ExtendedMaterials.Length > 0))      // Dla każdego Submesha tworzymy materiał i 
            {                                                                       // wczytujemy właściwą teksturę
                Textures = new Texture[ExtendedMaterials.Length];
                Materials = new Material[ExtendedMaterials.Length];
                for (int i = 0; i < ExtendedMaterials.Length; i++)
                {
                    Materials[i] = ExtendedMaterials[i].Material3D;
                    Materials[i].Ambient = Materials[i].Diffuse;
                    if ((ExtendedMaterials[i].TextureFilename != null) && (ExtendedMaterials[i].TextureFilename != string.Empty))
                    {
                        Textures[i] = TextureLoader.FromFile(dev, ExtendedMaterials[i].TextureFilename);
                    }
                }
            }
        }
    }
            
    
    public class WinForm : System.Windows.Forms.Form        // Tworzymy klasę WinForm pochodną standardowej formy z WinAPI
    {
        private Device device;  //tu pierwsza nowość. Zmienna typu Device.

        private MyObject texturedbox;
        private System.ComponentModel.Container components = null;
        private float t = 0;

        public WinForm()  // konstruktor - inicjalizujemy komponenty formy (tak naprawdę to ich nie ma, ale wygodniej się pracuje w VS)
        {
            InitializeComponent();
            
        }

        
        public void InitializeCamera() // To jest nowe w tej wersji. Tworzymy kamerę.
        {
            float aspect_ratio = this.Width / (float) this.Height;

            float q = 2.0f + (float)Math.Sin(4*t); //q - pomocnicza zmienna sterująca ruchem kamery
            device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, aspect_ratio, 1f, texturedbox.radius*12);  // macierz projekcji
            device.Transform.View = Matrix.LookAtLH(
                texturedbox.center - (new Vector3(3 * texturedbox.radius * (float)Math.Cos(t), 3 * texturedbox.radius * (float)Math.Sin(t), (float)3 * q * texturedbox.radius * (float)Math.Sin(t))), 
                texturedbox.center, 
                new Vector3(0, 1, 0)); // macierz widoku
            
            device.Transform.World = Matrix.Identity;

            device.Lights[0].Type = LightType.Directional;  // tworzymy dwa światła kierunkowe
            device.Lights[0].Diffuse = Color.Yellow;
            device.Lights[0].Direction = new Vector3(1,1,1);
            device.Lights[0].Enabled = true;
            device.Lights[1].Type = LightType.Directional;
            device.Lights[1].Diffuse = Color.Red;
            device.Lights[1].Direction = new Vector3(-1, -1, -1);
            device.Lights[1].Enabled = true;
            
            
        }

        public void InitializeMeshes()  // nowa metoda wczytująca obiekt
        {
            texturedbox = new MyObject(ref device);
            texturedbox.LoadFromFile("c:\\x\\audi.x");
        }

        public void InitializeDevice() // Druga nowość. Inicjujemy urządzenie.
        {
            PresentParameters presentParams = new PresentParameters();
            presentParams.Windowed = true;
            presentParams.SwapEffect = SwapEffect.Discard;
            presentParams.AutoDepthStencilFormat = DepthFormat.D16;
            presentParams.EnableAutoDepthStencil = true;
            device = new Device(0, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, presentParams);
           
       
            device.SamplerState[0].MinFilter = TextureFilter.Anisotropic;
            device.SamplerState[0].MagFilter = TextureFilter.Anisotropic;
            

        }

        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) //Trzecia nowość. W zdarzeniu OnPaint będziemy rysować. 
        {

            InitializeCamera(); //inicjalizacji kamerki
            device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkSlateBlue, 1.0f, 0);

            
              device.BeginScene(); // początek sceny 
              texturedbox.Render();
         
              device.EndScene();  //koniec sceny

              t += 0.01f;
            device.Present(); //tu informujemy, że Device już jest gotowe.

            this.Invalidate(); //to po to aby windows przerysował formę


        }

        protected override void Dispose(bool disposing) // tu zwalniamy wszystkie komponenty
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }
        private void InitializeComponent() // tu metoda inicjująca komponenty
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true); //tu drugi dodatek, aby Windows dobrze przerysowywał okno.
 
            this.ClientSize = new System.Drawing.Size(500, 500);
            this.Name = "WinForm";
            this.Text = "DirectX in C#";
            

        }

        static void Main()  // tu uruchamiamy aplikację.
        {
            using (WinForm dx_form = new WinForm())
            {
                dx_form.InitializeDevice(); // Czwarta nowość. Trzeba zainicjować Device
                dx_form.InitializeMeshes();
                Application.Run(dx_form);
            }
        }

       
    }
}
 